// Copyright 2019 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <assert.h>

#include "encoder.h"
#include "flow.h"

#include "buffer.h"
#include "media_type.h"

#ifdef RK_MOVE_DETECTION
#include "move_detection_flow.h"
#endif

// When the resolution is 2688x1520,
// the average encoding takes 12ms.
//
// TO DO: Dynamic calculate time cost.
#define ENC_CONST_MAX_TIME 12000 //us

namespace easymedia {

static bool encode(Flow *f, MediaBufferVector &input_vector);

class VideoEncoderFlow : public Flow {
public:
  VideoEncoderFlow(const char *param);
  virtual ~VideoEncoderFlow() {
    AutoPrintLine apl(__func__);
    StopAllThread();
  }
  static const char *GetFlowName() { return "video_enc"; }
  int Control(unsigned long int request, ...);
  void Dump(std::string &dump_info) override;

  MediaConfig GetConfig() {
    MediaConfig cfg;
    memset(&cfg, 0, sizeof(cfg));
    if (enc)
      cfg = enc->GetConfig();
    return cfg;
  }

private:
  std::shared_ptr<VideoEncoder> enc;
  bool extra_output;
  bool extra_merge;
  std::list<std::shared_ptr<MediaBuffer>> extra_buffer_list;
#ifdef RK_MOVE_DETECTION
  MoveDetectionFlow *md_flow;
#endif //RK_MOVE_DETECTION
  friend bool encode(Flow *f, MediaBufferVector &input_vector);
};

bool encode(Flow *f, MediaBufferVector &input_vector) {
  VideoEncoderFlow *vf = (VideoEncoderFlow *)f;
  std::shared_ptr<VideoEncoder> enc = vf->enc;
  std::shared_ptr<MediaBuffer> &src = input_vector[0];
  std::shared_ptr<MediaBuffer> dst, extra_dst;

  if (!src)
    return false;

  dst = std::make_shared<MediaBuffer>(); // TODO: buffer pool
  if (!dst) {
    LOG_NO_MEMORY();
    return false;
  }
  if (vf->extra_output) {
    extra_dst = std::make_shared<MediaBuffer>();
    if (!extra_dst) {
      LOG_NO_MEMORY();
      return false;
    }
  }

#ifdef RK_MOVE_DETECTION
  std::shared_ptr<MediaBuffer> md_info;
  if (vf->md_flow) {
    int smartp_enable = 0;
    enc->QueryChange(VideoEncoder::kMoveDetectionFlow,
      &smartp_enable, sizeof(int));
    if (!smartp_enable) {
      LOG("INFO: VEnc Flow: Wait for smartp configuration to take effect\n");
    } else {
      LOGD("VEnc Flow: LookForMdResult start!\n");

      MediaConfig mcfg = vf->GetConfig();
      int fps = mcfg.vid_cfg.frame_rate;
      if (fps <= 0) {
        LOG("ERROR: VEnc Flow: smartp must config fps correctly!");
        fps = 30;
      } else if (fps > 60)
        LOG("WARN: VEnc Flow: smartp may error in high fps:%d!", fps);

      int maximum_timeout = 1000000 / fps - ENC_CONST_MAX_TIME;
      md_info = vf->md_flow->LookForMdResult(
        src->GetAtomicClock(), maximum_timeout);
      if (md_info) {
#ifndef NDEBUG
        LOGD("VEnc Flow: get md info(cnt=%d): %p, %zuBytes\n",
          md_info->GetValidSize() / sizeof(INFO_LIST),
          md_info.get(), md_info->GetValidSize());
#endif
        if (md_info->GetSize() >= sizeof(INFO_LIST)) {
#ifndef NDEBUG
          INFO_LIST *info = (INFO_LIST *)md_info->GetPtr();
          while (info->flag) {
            LOGD("VEnc Flow: mdinfo: flag:%d, upleft:<%d, %d>, downright:<%d, %d>\n",
              info->flag, info->up_left[0], info->up_left[1],
              info->down_right[0], info->down_right[1]);
            info += 1;
          }
#endif
          src->SetRelatedSPtr(md_info);
        }
      } else
        LOG("ERROR: VEnc Flow: fate error get null md result\n");

      LOGD("VEnc Flow: LookForMdResult end!\n\n");
    }
  }
#endif //RK_MOVE_DETECTION

  if (0 != enc->Process(src, dst, extra_dst)) {
    LOG("encoder failed\n");
    return false;
  }

  bool ret = true;
  // when output fps less len input fps, enc->Proccess() may
  // return a empty mediabuff.
  if (dst->GetValidSize() > 0) {
    ret = vf->SetOutput(dst, 0);
    if (vf->extra_output)
      ret &= vf->SetOutput(extra_dst, 1);
  }

  return ret;
}

VideoEncoderFlow::VideoEncoderFlow(const char *param) : extra_output(false),
    extra_merge(false)
#ifdef  RK_MOVE_DETECTION
, md_flow(nullptr)
#endif
{
  std::list<std::string> separate_list;
  std::map<std::string, std::string> params;

  LOG("VEnc Flow: dump param:%s\n", param);
  if (!ParseWrapFlowParams(param, params, separate_list)) {
    SetError(-EINVAL);
    return;
  }
  std::string &codec_name = params[KEY_NAME];
  if (codec_name.empty()) {
    LOG("missing codec name\n");
    SetError(-EINVAL);
    return;
  }

  std::string &extra_merge_value = params[KEY_NEED_EXTRA_MERGE];
  if (!extra_merge_value.empty()) {
    extra_merge = !!std::stoi(extra_merge_value);
  }

  const char *ccodec_name = codec_name.c_str();
  // check input/output type
  std::string &&rule = gen_datatype_rule(params);
  if (rule.empty()) {
    SetError(-EINVAL);
    return;
  }
  if (!REFLECTOR(Encoder)::IsMatch(ccodec_name, rule.c_str())) {
    LOG("Unsupport for video encoder %s : [%s]\n", ccodec_name, rule.c_str());
    SetError(-EINVAL);
    return;
  }

  std::string &enc_param_str = separate_list.back();
  std::map<std::string, std::string> enc_params;

  if (!parse_media_param_map(enc_param_str.c_str(), enc_params)) {
    SetError(-EINVAL);
    return;
  }
  // copy data type to enc params.
  std::string::size_type idx;
  idx = enc_param_str.find(KEY_OUTPUTDATATYPE);
  if (idx == enc_param_str.npos)
    PARAM_STRING_APPEND(enc_param_str, KEY_OUTPUTDATATYPE, params[KEY_OUTPUTDATATYPE]);
  if (enc_params[KEY_INPUTDATATYPE].empty())
    enc_params[KEY_INPUTDATATYPE] = params[KEY_INPUTDATATYPE];
  if (enc_params[KEY_OUTPUTDATATYPE].empty())
    enc_params[KEY_OUTPUTDATATYPE] = params[KEY_OUTPUTDATATYPE];

  MediaConfig mc;
  if (!ParseMediaConfigFromMap(enc_params, mc)) {
    SetError(-EINVAL);
    return;
  }

  auto encoder = REFLECTOR(Encoder)::Create<VideoEncoder>(
      ccodec_name, enc_param_str.c_str());
  if (!encoder) {
    LOG("Fail to create video encoder %s<%s>\n", ccodec_name,
        enc_param_str.c_str());
    SetError(-EINVAL);
    return;
  }

  if (!encoder->InitConfig(mc)) {
    LOG("Fail to init config, %s\n", ccodec_name);
    SetError(-EINVAL);
    return;
  }

  std::string roi_region_str = enc_params[KEY_ROI_REGIONS];
  if (!roi_region_str.empty()) {
    int roi_regions_cnt = 0;
    std::vector<EncROIRegion> roi_regions;
    roi_regions = StringToRoiRegions(roi_region_str);
    roi_regions_cnt = roi_regions.size();
    if (roi_regions_cnt) {
      EncROIRegion *regions =
        (EncROIRegion *) malloc(roi_regions_cnt * sizeof(EncROIRegion));
      for (int i = 0; i < roi_regions_cnt; i++) {
        (regions + i)->x = roi_regions[i].x;
        (regions + i)->y = roi_regions[i].y;
        (regions + i)->w = roi_regions[i].w;
        (regions + i)->h = roi_regions[i].h;
        (regions + i)->intra = roi_regions[i].intra;
        (regions + i)->quality = roi_regions[i].quality;
        (regions + i)->qp_area_idx = roi_regions[i].qp_area_idx;
        (regions + i)->area_map_en = roi_regions[i].area_map_en;
        (regions + i)->abs_qp_en = roi_regions[i].abs_qp_en;
        LOG("VEnc Flow: Roi Regions[%d]: (%d,%d,%d,%d,%d,%d,%d,%d,%d)\n",
          i, roi_regions[i].x, roi_regions[i].y, roi_regions[i].w, roi_regions[i].h,
          roi_regions[i].intra, roi_regions[i].quality, roi_regions[i].qp_area_idx,
          roi_regions[i].area_map_en, roi_regions[i].abs_qp_en);
      }

      auto pbuff = std::make_shared<ParameterBuffer>(0);
      pbuff->SetPtr(regions, roi_regions_cnt * sizeof(EncROIRegion));
      encoder->RequestChange(VideoEncoder::kROICfgChange, pbuff);
    }
  }

  void *extra_data = nullptr;
  size_t extra_data_size = 0;
  encoder->GetExtraData(&extra_data, &extra_data_size);
  // TODO: if not h264
  const std::string &output_dt = enc_params[KEY_OUTPUTDATATYPE];

  enc = encoder;

  SlotMap sm;
  sm.input_slots.push_back(0);
  sm.output_slots.push_back(0);
  if (params[KEY_NEED_EXTRA_OUTPUT] == "y") {
    extra_output = true;
    sm.output_slots.push_back(1);
  }
  sm.process = encode;
  sm.thread_model = Model::ASYNCCOMMON;
  sm.mode_when_full = InputMode::DROPFRONT;
  sm.input_maxcachenum.push_back(3);
  if (!InstallSlotMap(sm, "VideoEncoderFlow", 40)) {
    LOG("Fail to InstallSlotMap, %s\n", ccodec_name);
    SetError(-EINVAL);
    return;
  }
  SetFlowTag("VideoEncoderFlow");

  if (extra_data && extra_data_size > 0 &&
      (output_dt == VIDEO_H264 || output_dt == VIDEO_H265)) {

    if (extra_merge) {
      std::shared_ptr<MediaBuffer> extra_buf = std::make_shared<MediaBuffer>();
      extra_buf->SetPtr(extra_data);
      extra_buf->SetValidSize(extra_data_size);
      extra_buf->SetUserFlag(MediaBuffer::kExtraIntra);
      SetOutput(extra_buf, 0);
    } else {
      if (output_dt == VIDEO_H264)
        extra_buffer_list = split_h264_separate((const uint8_t *)extra_data,
                                              extra_data_size, gettimeofday());
      else
        extra_buffer_list = split_h265_separate((const uint8_t *)extra_data,
                                              extra_data_size, gettimeofday());
      for (auto &extra_buffer : extra_buffer_list) {
        assert(extra_buffer->GetUserFlag() & MediaBuffer::kExtraIntra);
        SetOutput(extra_buffer, 0);
      }
    }

    if (extra_output) {
      std::shared_ptr<MediaBuffer> nullbuffer;
      SetOutput(nullbuffer, 1);
    }
  }
}

int VideoEncoderFlow::Control(unsigned long int request, ...) {
  va_list ap;
  va_start(ap, request);
  auto value = va_arg(ap, std::shared_ptr<ParameterBuffer>);
  va_end(ap);
  assert(value);

#ifdef RK_MOVE_DETECTION
  if (request == VideoEncoder::kMoveDetectionFlow) {
    if (value->GetSize() != sizeof(void **)) {
      LOG("ERROR: VEnc Flow: move detect config falied!\n");
      return -1;
    }
    md_flow = *((MoveDetectionFlow **)value->GetPtr());
    LOGD("VEnc Flow: md_flow:%p\n", md_flow);
  }
#endif //RK_MOVE_DETECTION

  enc->RequestChange(request, value);
  return 0;
}

void VideoEncoderFlow::Dump(std::string &dump_info) {
  const MediaConfig mc = GetConfig();
  char str_line[1024] = {0};

  DumpBase(dump_info);
  sprintf(str_line, "#Dump Flow(%s) advanced info:\r\n", GetFlowTag());
  dump_info.append(str_line);
  memset(str_line, 0, sizeof(str_line));
  sprintf(str_line, "  Name:%s\r\n", GetFlowName());
  dump_info.append(str_line);

  memset(str_line, 0, sizeof(str_line));
  if (mc.type == Type::Image) {
    dump_info.append("  CodecType: JPEG\r\n");
    sprintf(str_line, "  Input: %d(%d)x%d(%d) fmt:%s\r\n",
      mc.img_cfg.image_info.width, mc.img_cfg.image_info.vir_width,
      mc.img_cfg.image_info.height, mc.img_cfg.image_info.vir_height,
      PixFmtToString(mc.img_cfg.image_info.pix_fmt));
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  Qfactor:%d\n", mc.img_cfg.qfactor);
    dump_info.append(str_line);
  } else if (mc.type == Type::Video) {
    const VideoConfig &vcfg = mc.vid_cfg;
    const ImageConfig &imgcfg = vcfg.image_cfg;

    if (imgcfg.codec_type == CODEC_TYPE_H264)
      dump_info.append("  CodecType: H264\r\n");
    else if (imgcfg.codec_type == CODEC_TYPE_H265)
      dump_info.append("  CodecType: H265\r\n");
    else {
      LOG("ERROR: VEnc Flow: config fatal error!\n");
      return;
    }
    sprintf(str_line, "  Input: %d(%d)x%d(%d) fmt:%s\r\n",
      imgcfg.image_info.width, imgcfg.image_info.vir_width,
      imgcfg.image_info.height, imgcfg.image_info.vir_height,
      PixFmtToString(imgcfg.image_info.pix_fmt));
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  QpArray: init:%d min:%d, max:%d, step:%d, min_i:%d, max_i:%d\r\n",
      vcfg.qp_init, vcfg.qp_min, vcfg.qp_max, vcfg.qp_step, vcfg.qp_min_i, vcfg.qp_max_i);
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  BitRate: target:%d, min:%d, max:%d\r\n",
      vcfg.bit_rate, vcfg.bit_rate_min, vcfg.bit_rate_max);
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  FrameRate: in:%d/%d, out:%d/%d\r\n",
      vcfg.frame_in_rate, vcfg.frame_in_rate_den,
      vcfg.frame_rate, vcfg.frame_rate_den);
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  GopSize: %d\r\n", vcfg.gop_size);
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  RcQuality: %s\r\n", vcfg.rc_quality);
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  RcMode: %s\r\n", vcfg.rc_mode);
    dump_info.append(str_line);
    memset(str_line, 0, sizeof(str_line));
    sprintf(str_line, "  FullRange: %s\r\n", vcfg.full_range ? "Enable" : "Disable");
    dump_info.append(str_line);

    if (imgcfg.codec_type == CODEC_TYPE_H264) {
      memset(str_line, 0, sizeof(str_line));
      sprintf(str_line, "  Trans8x8: %s\r\n", vcfg.trans_8x8 ? "Enable" : "Disable");
      dump_info.append(str_line);
      memset(str_line, 0, sizeof(str_line));
      sprintf(str_line, "  H264Level: %d\r\n", vcfg.level);
      dump_info.append(str_line);
      memset(str_line, 0, sizeof(str_line));
      sprintf(str_line, "  H264Profile: %d\r\n", vcfg.profile);
      dump_info.append(str_line);
    }
  } else {
    LOG("ERROR: VEnc Flow: Dump: to do...!\n");
    return;
  }

  return;
}

DEFINE_FLOW_FACTORY(VideoEncoderFlow, Flow)
// type depends on encoder
const char *FACTORY(VideoEncoderFlow)::ExpectedInputDataType() { return ""; }
const char *FACTORY(VideoEncoderFlow)::OutPutDataType() { return ""; }

} // namespace easymedia
